/************************************************
 * MirrorOS Kernel Drivers
 * RamFS Driver
 * Copyright 2008 Matthew Iselin
 ***********************************************/

// Defines

// register offsets
#define ATA_FEATS	1
#define ATA_SCOUNT	2
#define ATA_SNUM	3
#define ATA_CLOW	4
#define ATA_CHI		5
#define ATA_HDEV	6
#define ATA_COMM	7
#define ATA_DATA	0
#define ATA_ERR		1
#define ATA_STAT	7

// seeking stuff
#define SEEK_SET	0
#define SEEK_CUR	1
#define SEEK_END	2

// mount format string
// $ = device id
#define MOUNT_FORMAT_STRING "/dev/hd$"

// Prototypes

int dprintf( const char* fmt, ... );

void InstallDevice(	char* prefix,
					unsigned int s_open,
					unsigned int s_read,
					unsigned int s_write,
					unsigned int s_seek,
					unsigned int s_ioctl,
					unsigned int s_stat,
					unsigned int s_close,
					int blocksize
					);

// Types

// struct stat stuff
#ifdef __CYGWIN__
#include <cygwin/stat.h>
#ifdef _COMPILING_NEWLIB
#define stat64 __stat64
#endif
#else
struct	stat
{
  short		st_dev;
  unsigned short		st_ino;
  unsigned int	st_mode;
  unsigned short	st_nlink;
  unsigned short		st_uid;
  unsigned short		st_gid;
  short		st_rdev;
  long		st_size;
  /* SysV/sco doesn't have the rest... But Solaris, eabi does.  */
#if defined(__svr4__) && !defined(__PPC__) && !defined(__sun__)
  unsigned long	st_atime;
  unsigned long	st_mtime;
  unsigned long	st_ctime;
#else
  unsigned long	st_atime;
  long		st_spare1;
  unsigned long	st_mtime;
  long		st_spare2;
  unsigned long	st_ctime;
  long		st_spare3;
  long		st_blksize;
  long		st_blocks;
  long	st_spare4[2];
#endif
};
#endif

typedef struct ident_device_t {
	unsigned short	config;		/* lots of obsolete bit flags */
	unsigned short	cyls;		/* "physical" cyls */
	unsigned short	reserved2;	/* reserved (word 2) */
	unsigned short	heads;		/* "physical" heads */
	unsigned short	track_bytes;	/* unformatted bytes per track */
	unsigned short	sector_bytes;	/* unformatted bytes per sector */
	unsigned short	sectors;	/* "physical" sectors per track */
	unsigned short	vendor0;	/* vendor unique */
	unsigned short	vendor1;	/* vendor unique */
	unsigned short	vendor2;	/* vendor unique */
	unsigned char	serial_no[20];	/* 0 = not_specified */
	unsigned short	buf_type;
	unsigned short	buf_size;	/* 512 byte increments; 0 = not_specified */
	unsigned short	ecc_bytes;	/* for r/w long cmds; 0 = not_specified */
	unsigned char	fw_rev[8];	/* 0 = not_specified */
	unsigned char	model[40];	/* 0 = not_specified */
	unsigned char	max_multsect;	/* 0=not_implemented */
	unsigned char	vendor3;	/* vendor unique */
	unsigned short	dword_io;	/* 0=not_implemented; 1=implemented */
	unsigned char	vendor4;	/* vendor unique */
	unsigned char	capability;	/* bits 0:DMA 1:LBA 2:IORDYsw 3:IORDYsup*/
	unsigned short	reserved50;	/* reserved (word 50) */
	unsigned char	vendor5;	/* vendor unique */
	unsigned char	tPIO;		/* 0=slow, 1=medium, 2=fast */
	unsigned char	vendor6;	/* vendor unique */
	unsigned char	tDMA;		/* 0=slow, 1=medium, 2=fast */
	unsigned short	field_valid;	/* bits 0:cur_ok 1:eide_ok */
	unsigned short	cur_cyls;	/* logical cylinders */
	unsigned short	cur_heads;	/* logical heads */
	unsigned short	cur_sectors;	/* logical sectors per track */
	unsigned short	cur_capacity0;	/* logical total sectors on drive */
	unsigned short	cur_capacity1;	/*  (2 words, misaligned int)     */
	unsigned char	multsect;	/* current multiple sector count */
	unsigned char	multsect_valid;	/* when (bit0==1) multsect is ok */
	unsigned int	lba_capacity;	/* total number of sectors */
	unsigned short	dma_1word;	/* single-word dma info */
	unsigned short	dma_mword;	/* multiple-word dma info */
	unsigned short  eide_pio_modes; /* bits 0:mode3 1:mode4 */
	unsigned short  eide_dma_min;	/* min mword dma cycle time (ns) */
	unsigned short  eide_dma_time;	/* recommended mword dma cycle time (ns) */
	unsigned short  eide_pio;       /* min cycle time (ns), no IORDY  */
	unsigned short  eide_pio_iordy; /* min cycle time (ns), with IORDY */
	unsigned short	words69_70[2];	/* reserved words 69-70 */
	unsigned short	words71_74[4];	/* reserved words 71-74 */
	unsigned short  queue_depth;	/*  */
	unsigned short  words76_79[4];	/* reserved words 76-79 */
	unsigned short  major_rev_num;	/*  */
	unsigned short  minor_rev_num;	/*  */
	unsigned short  command_set_1;	/* bits 0:Smart 1:Security 2:Removable 3:PM */
	unsigned short  command_set_2;	/* bits 14:Smart Enabled 13:0 zero */
	unsigned short  cfsse;		/* command set-feature supported extensions */
	unsigned short  cfs_enable_1;	/* command set-feature enabled */
	unsigned short  cfs_enable_2;	/* command set-feature enabled */
	unsigned short  csf_default;	/* command set-feature default */
	unsigned short  dma_ultra;	/*  */
	unsigned short	word89;		/* reserved (word 89) */
	unsigned short	word90;		/* reserved (word 90) */
	unsigned short	CurAPMvalues;	/* current APM values */
	unsigned short	word92;		/* reserved (word 92) */
	unsigned short	hw_config;	/* hardware config */
	unsigned short  words94_125[32];/* reserved words 94-125 */
	unsigned short	last_lun;	/* reserved (word 126) */
	unsigned short	word127;	/* reserved (word 127) */
	unsigned short	dlf;		/* device lock function
					 * 15:9	reserved
					 * 8	security level 1:max 0:high
					 * 7:6	reserved
					 * 5	enhanced erase
					 * 4	expire
					 * 3	frozen
					 * 2	locked
					 * 1	en/disabled
					 * 0	capability
					 */
	unsigned short  csfo;		/* current set features options
					 * 15:4	reserved
					 * 3	auto reassign
					 * 2	reverting
					 * 1	read-look-ahead
					 * 0	write cache
					 */
	unsigned short	words130_155[26];/* reserved vendor words 130-155 */
	unsigned short	word156;
	unsigned short	words157_159[3];/* reserved vendor words 157-159 */
	unsigned short	words160_255[95];/* reserved words 160-255 */
} IDENTIFYDEVICE;

// information about controllers - the first entry in base is the default
// entry that is meant to be used, the second entry is the auxillary port
// for the controller
typedef struct {
   unsigned short base[2];
} ata_ctl_ports;

// an ATA device attached to the system
struct ata_dev {
	int valid;
	short ioport;
	short dev;
	IDENTIFYDEVICE devident;
	char mount[32];
};

// an internal descriptor describing an open connection to a drive
struct intdesc {
	int device; // offset into the ata_devices array of the drive itself
	unsigned long offset; // the current offset (in lba28 notation) into the drive itself
	int valid; // whether this descriptor references a valid device or not
};

// Variables

// controllers
ata_ctl_ports ata_ctl_info[] = {
   { .base = {0x1f0, 0x3f0} },
   { .base = {0x170, 0x370} },
   { .base = {0x1e8, 0x3e0} },
   { .base = {0x168, 0x360} }
};

// ATA devices
struct ata_dev ata_devices[16];

// descriptors
struct intdesc desctable[1024];

// Functions

// the prologue to an LBA28 disk operation
void lba28_prolog( int lba_addr, int atadev, int numsectors )
{
	// wait until the device is ready
	while( ( inb( ata_devices[atadev].ioport + 0x206 ) & 0x80) ) { kSchedule( 0 ); };
	
	// null to features
	outb( ata_devices[atadev].ioport + ATA_FEATS, 0x00 );

	// number of sectors
	outb( ata_devices[atadev].ioport + ATA_SCOUNT, numsectors /*0x01*/ );

	// block address
	outb( ata_devices[atadev].ioport + ATA_SNUM, (unsigned char) lba_addr );
	outb( ata_devices[atadev].ioport + ATA_CLOW, (unsigned char) (lba_addr >> 8) );
	outb( ata_devices[atadev].ioport + ATA_CHI, (unsigned char) (lba_addr >> 16) );

	// drive id et al
	unsigned char hdev = 0xE0 | ( ata_devices[atadev].dev << 4 ) | ( lba_addr >> 24 );
	outb( ata_devices[atadev].ioport + ATA_HDEV, hdev );
}

// reads a sector from the disk (using LBA28 addressing - this is actually the only real way to do this, as the device I/O system doesn't support 48-bit addressing. it's fine, because lba28 works up to (2^28 * 512) bytes)
int lba28_ReadSector( int atadev, int numsectors, int lba_addr, char* data )
{
	// run the prologue
	lba28_prolog( lba_addr, atadev, numsectors );
	
	// start the read
	outb( ata_devices[atadev].ioport + ATA_COMM, 0x20 );
	
	// wait until the device is ready to read
	while( ( inb( ata_devices[atadev].ioport + ATA_COMM ) & 0x80 ) ) { kSchedule( 0 ); };
	
	// check for any errors
	unsigned char err = inb( ata_devices[atadev].ioport + 0x206 ) & 0x1;
	unsigned char errreg = inb( ata_devices[atadev].ioport + ATA_FEATS );
	
	// fail at life if we get an error
	if( err )
		return -1;
	
	// wait until the request is done - let other programs run while we wait
	while( ! ( inb( ata_devices[atadev].ioport + 0x206 ) & 0x08 ) ) { kSchedule( 0 ); };
	
	// read in the data from the sector
	int i;
	for( i = 0; i < (ata_devices[atadev].devident.sector_bytes * numsectors); i += sizeof( unsigned short ) )
	{
		unsigned short tmpword = inw( ata_devices[atadev].ioport );
		*((unsigned short*) &data[i]) = tmpword;
	}
	
	// return the number of sectors read
	return (i / ata_devices[atadev].devident.sector_bytes);
}

// writes a sector to the disk (using LBA28 addressing - this is actually the only real way to do this, as the device I/O system doesn't support 48-bit addressing. it's fine, because lba28 works up to (2^28 * 512) bytes)
int lba28_WriteSector( int atadev, int numsectors, int lba_addr, char* data )
{
	// run the prologue
	lba28_prolog( lba_addr, atadev, numsectors );
	
	// start the write
	outb( ata_devices[atadev].ioport + ATA_COMM, 0x30 );
	
	// check for any errors
	unsigned char err = inb( ata_devices[atadev].ioport + 0x206 ) & 0x1;
	unsigned char errreg = inb( ata_devices[atadev].ioport + ATA_FEATS );
	
	// fail at life if we get an error
	if( err )
		return -1;
	
	// wait until the request is done - let other programs run while we wait
	while( ! ( inb( ata_devices[atadev].ioport + 0x206 ) & 0x08 ) ) { kSchedule( 0 ); };
	
	// write the data to the sector
	int i;
	for( i = 0; i < (ata_devices[atadev].devident.sector_bytes * numsectors); i += sizeof( unsigned short ) )
		outw( ata_devices[atadev].ioport, *((unsigned short*) &data[i]) );
	
	// return the number of sectors written
	return (i / ata_devices[atadev].devident.sector_bytes);
}

// runs the "IDENTIFY DEVICE" command
IDENTIFYDEVICE identify_device( int base, int drv )
{
	// choose the drive
	outb( base + ATA_HDEV, 0xE0 | drv );
	
	// send & wait
	outb( base + ATA_COMM, 0xEC );
	
	// wait until we're ready
	while( (inb( base + 0x206 ) & 0x80) );
	
	// read in the status byte
	unsigned char ident_stat = inb( base + ATA_COMM );
	
	// error?
	if( ident_stat & 0x1 )
	{
		// TODO: handle packet device identification, page 105 of the ATA specs
		return;
	}
	
	// return data
	IDENTIFYDEVICE ident;
	
	// wait for the data to arrive - but timeout if it doesn't
	if( ( inb( base + ATA_COMM ) & 0x08 ) )
	{
		// counter
		int idx;
		
		// read in all the data
		unsigned short* identdata = (unsigned short*) &ident;
		for( idx = 0; idx < 255; idx++ )
		{
			identdata[idx] = inw( base );
		}
	}

	// return the entire structure
	return ident;
}

// detects ATA drives
int DetectDrives()
{	
	// drive id, internal counters
	int drvi, x, y, z, intdrive = 0;
	
	// loop through each possible drive and controller
	for( drvi = 0, x = 0; x < ( sizeof( ata_ctl_info ) / sizeof( ata_ctl_ports ) ); x++ )
	{
		// each controller
		for( z = 0; z < 2; z++ )
		{
			// master/slave
			for(y = 0; y < 2; ++y)
			{
				// output the drive and head to the drive/head port
				outb(ata_ctl_info[x].base[z] + ATA_HDEV, ((drvi << 4) | 0xa0 | (y << 4)));
				
				// the bios uses this to delay long enough for the I/O request to work (saves having to give up the timeslice, which is much slower than I/O)
				outb( 0xEB, 0 );

				// check for the ready bit, the drive seek completion bit, no write faults, and no errors
				if((((unsigned char)inb(ata_ctl_info[x].base[z] + ATA_STAT) & (0x40 | 0x10 | 0x20 | 0x01)) == 0x50))
				{					
					// install it
					struct ata_dev d;
					d.valid = 1;
					d.ioport = ata_ctl_info[x].base[z];
					d.dev = drvi;
					d.devident = identify_device( d.ioport, d.dev );
					
					// put the format string into the buffer
					sprintf( d.mount, "/dev/hd%d", drvi );
					
					// insert into the list
					ata_devices[drvi] = d;
					
					// new drive added
					drvi++;
					
					// increment our internal drive id
					intdrive++;
				}
			}
		}
	}
}

// opens a drive for I/O
int ata_open( const char* path, int flags )
{
	// find one that matches in the list
	int i;
	for( i = 0; i < 16; i++ )
		if( ata_devices[i].valid )
			if( strcmp( ata_devices[i].mount, path ) == 0 )
				break;
	if( i == 16 )
		return -1;
	
	// find a valid descriptor for it
	int d;
	for( d = 0; d < 1024; d++ )
		if( desctable[d].valid )
			break;
	if( d == 1024 )
		return -1;
	
	// setup the descriptor
	desctable[d].valid = 0;
	desctable[d].device = i;
	desctable[d].offset = 0;
	
	// return its id
	return d;
}

// reads a number of sectors from a drive
int ata_read( int fd, char* buff, int len )
{
	// check for a valid descriptor
	if( fd < 0 || fd >= 1024 || desctable[fd].valid == 1 || ata_devices[desctable[fd].device].valid == 0 )
		return -1;
	
	// find out how many total sectors can be read in at a time
	int i, total_sectors = 0;
	if( ata_devices[desctable[fd].device].devident.max_multsect == 0 ) // 0 = not supported
		total_sectors = 1;
	else
		total_sectors = ata_devices[desctable[fd].device].devident.max_multsect;

	// special case: where the length is lower than the number of sectors we *can* read, only read in
	// what we need
	if( len < total_sectors )
		total_sectors = len;
	
	// read in the sectors
	for( i = 0; i < len; i += total_sectors )
	{
		// check for a case where the number we will be reading is lower than the total sectors number
		if( (len - i) < total_sectors )
			total_sectors = (len - i);
		
		// run the read command
		int ret = lba28_ReadSector( desctable[fd].device, total_sectors, desctable[fd].offset + i, buff + (ata_devices[desctable[fd].device].devident.sector_bytes * i) );
	}
	
	// increment the offset by the number of sectors read
	desctable[fd].offset += i;
	
	// return the number read in
	return i;
}

// writes a number of sectors to a drive
int ata_write( int fd, char* buff, int len )
{
	// check for a valid descriptor
	if( fd < 0 || fd >= 1024 || desctable[fd].valid == 1 || ata_devices[desctable[fd].device].valid == 0 )
		return -1;
	
	// find out how many total sectors can be read in at a time
	int i, total_sectors = 0;
	if( ata_devices[desctable[fd].device].devident.max_multsect == 0 ) // 0 = not supported
		total_sectors = 1;
	else
		total_sectors = ata_devices[desctable[fd].device].devident.max_multsect;

	// special case: where the length is lower than the number of sectors we *can* read, only read in
	// what we need
	if( len < total_sectors )
		total_sectors = len;
	
	// read in the sectors
	for( i = 0; i < len; i += total_sectors )
	{
		// check for a case where the number we will be reading is lower than the total sectors number
		if( (len - i) < total_sectors )
			total_sectors = (len - i);
		
		// run the read command
		int ret = lba28_WriteSector( desctable[fd].device, total_sectors, desctable[fd].offset + i, buff + (ata_devices[desctable[fd].device].devident.sector_bytes * i) );
	}
	
	// increment the offset by the number of sectors written
	desctable[fd].offset += i;
	
	// return the number read in
	return i;
}

// seeks to a new offset into the device
unsigned long ata_seek( int fd, unsigned long off, int set )
{	
	// check for a valid descriptor
	if( fd < 0 || fd >= 1024 || desctable[fd].valid == 1 || ata_devices[desctable[fd].device].valid == 0 )
		return -1;
	
	// the maximum offset
	unsigned long maxoff = ata_devices[desctable[fd].device].devident.lba_capacity;
	
	// what's the set?
	switch( set )
	{
		case SEEK_SET:
			if( off > maxoff )
				off = maxoff;
			desctable[fd].offset = off;
			break;
		case SEEK_CUR:
			desctable[fd].offset += off;
			if( desctable[fd].offset > maxoff )
				desctable[fd].offset = maxoff;
			break;
		case SEEK_END:
			desctable[fd].offset = maxoff - off;
			if( off > maxoff )
				desctable[fd].offset = 0;
			break;
	}
	
	// return the new position
	return desctable[fd].offset;
}

// controls the device
int ata_ioctl( int fd, int action, int data )
{
	// nothing to do here
	return 0;
}

// gets information about a specific open descriptor
int ata_stat( int fd, struct stat* buf )
{
	// check for a valid descriptor
	if( fd < 0 || fd >= 1024 || desctable[fd].valid == 1 || ata_devices[desctable[fd].device].valid == 0 )
		return -1;
	
	// set the mode and the maximum block count
	buf->st_size = ata_devices[desctable[fd].device].devident.lba_capacity * ata_devices[desctable[fd].device].devident.sector_bytes;
	buf->st_blksize = ata_devices[desctable[fd].device].devident.sector_bytes;
	buf->st_blocks = ata_devices[desctable[fd].device].devident.lba_capacity;
	
	// success
	return 0;
}

// closes a descriptor
int ata_close( int fd )
{
	// check for a valid descriptor
	if( fd < 0 || fd >= 1024 || desctable[fd].valid == 1 || ata_devices[desctable[fd].device].valid == 0 )
		return -1;
	
	// close the descriptor
	desctable[fd].valid = 1;
	
	// success
	return 0;
}

// driver entry point
void dmain( int argc, char* argv[] )
{	
	// set all ata devices as invalid, as well as all descriptors as invalid
	int i;
	for( i = 0; i < 16; i++ )
		ata_devices[i].valid = 0;
	for( i = 0; i < 1024; i++ )
		desctable[i].valid = 1;
	
	// detect the drives installed (also installs them into the device list)
	DetectDrives();
	
	// install all the mount points
	for( i = 0; i < 16; i++ )
		if( ata_devices[i].valid )
			InstallDevice( ata_devices[i].mount, (unsigned int) ata_open, (unsigned int) ata_read, (unsigned int) ata_write, (unsigned int) ata_seek, (unsigned int) ata_ioctl, (unsigned int) ata_stat, (unsigned int) ata_close, ata_devices[i].devident.sector_bytes );
}
